package com.math.aistudykbr.utils;

import com.math.aistudykbr.config.JwtProperties;
import com.math.aistudykbr.constant.MessageConstants;
import com.math.aistudykbr.exception.UnauthorizedException;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

@Component
@RequiredArgsConstructor
@Slf4j
public class JwtUtil {
    private final JwtProperties jwtProperties;

    // 生成jwt令牌
    public String generateToken(Long userId) {
        Map<String, Object> claims = new HashMap<>();
        claims.put("userId",userId);
        claims.put("expiration" , jwtProperties.getExpiration());   // 过期时间
        claims.put("refreshExpiration", jwtProperties.getRefreshExpiration());   // 刷新过期时间
//        claims.put("secret", jwtProperties.getSecret());   // 密钥
        claims.put("jti", UUID.randomUUID().toString());    // 添加唯一标识符
        return createToken(claims, jwtProperties.getSecret());
    }

    private String createToken(Map<String, Object> claims, String subject) {
        return Jwts.builder()
                // 设置 JWT 的所有声明信息（claims）
                .setClaims(claims)
                // 设置主题（通常是用户名或用户标识）
                .setSubject(subject)
                // 设置令牌的签发时间为当前时间
                .setIssuedAt(new Date(System.currentTimeMillis()))
                // 设置令牌的过期时间，当前时间加上配置的过期时长
                .setExpiration(new Date(System.currentTimeMillis() + jwtProperties.getExpiration()))
                // 使用 HS512 签名算法和配置的密钥对令牌进行签名，以确保令牌的完整性和真实性
                .signWith(SignatureAlgorithm.HS512, jwtProperties.getSecret()) // 使用更强的算法
                .compact();
    }

    // 生成刷新令牌
    public String generateRefreshToken(Long userId) {
        Map<String, Object> claims = new HashMap<>();
        claims.put("userId", userId);
        claims.put("jti", UUID.randomUUID().toString()); // 添加唯一标识符
        return Jwts.builder()
                .setClaims(claims)
                .setSubject(jwtProperties.getSecret())
                .setIssuedAt(new Date(System.currentTimeMillis()))
                .setExpiration(new Date(System.currentTimeMillis() + jwtProperties.getRefreshExpiration())) // 刷新令牌过期时间
                .signWith(SignatureAlgorithm.HS512, jwtProperties.getSecret())
                .compact();
    }

    // 解析 JWT 令牌
    public Claims parseToken(String token) {
        return Jwts.parser()
                .setSigningKey(jwtProperties.getSecret())
                .parseClaimsJws(token)
                .getBody();
    }

    // 校验 JWT 令牌的有效性
    public void validateToken(String token) throws UnauthorizedException {
        // 1. 校验token是否为空
        if (token == null || token.isEmpty()) {
            log.error("token is null or empty");
            throw new UnauthorizedException(MessageConstants.TokenNotFound);
        }

        // 2. 校验并解析jwt
        try {
            Jwts.parser().setSigningKey(jwtProperties.getSecret()).parseClaimsJws(token);
        } catch (Exception e) {
            log.error("token is invalid");
            throw new UnauthorizedException(MessageConstants.TokenInvalid);
        }

        // 3. 校验过期时间
        Claims claims = Jwts.parser()
                .setSigningKey(jwtProperties.getSecret())
                .parseClaimsJws(token)
                .getBody();
        Date expiration = claims.getExpiration();
        if (expiration == null || expiration.before(new Date())) {
            log.error("token is expired");
            throw new UnauthorizedException(MessageConstants.TokenExpire);
        }
    }

    // 提取用户 ID
    public Long extractUserId(String token) {
        Object userIdObj = parseToken(token).get("userId");
        if (userIdObj instanceof Number) {
            return ((Number) userIdObj).longValue(); // 转换为 Long
        }
        throw new IllegalArgumentException("User ID is not a number");
    }
    // 提取 JWT ID
    public String extractJti(String token) {
        return (String) parseToken(token).get("jti");
    }

    // 提取过期时间
    public Date extractExpiration(String token) {
        return parseToken(token).getExpiration();
    }

    // 检查令牌是否过期
    public Boolean isTokenExpired(String token) {
        return extractExpiration(token).before(new Date());
    }

    /**
     * 刷新token
     * @param oldToken 原token
     * @return 新token
     */
    public String refreshToken(String oldToken) throws UnauthorizedException {
        try {
            // 1. 解析旧token并验证有效性
            Claims claims = parseToken(oldToken);

            // 2. 获取令牌签发时间和当前服务器配置的刷新有效期
            Date issuedAt = claims.getIssuedAt();
            long refreshExpirationMs = jwtProperties.getRefreshExpiration();
            Date refreshDeadline = new Date(issuedAt.getTime() + refreshExpirationMs);

            // 3. 验证是否在刷新窗口期内
            Date currentTime = new Date();
            if (currentTime.after(refreshDeadline)) {
                log.error("刷新令牌已超过最大有效期");
                throw new UnauthorizedException(MessageConstants.TokenExpire);
            }

            // 4. 即使原令牌过期，只要在刷新窗口期内仍允许刷新
            if (claims.getExpiration().before(currentTime)) {
                log.info("刷新已过期的令牌，签发时间: {}", issuedAt);
            }

            // 5. 生成全新令牌（更新签发时间和过期时间）
            Long userId = extractUserId(oldToken);
            return generateToken(userId);

        } catch (UnauthorizedException e) {
            throw e;
        } catch (Exception e) {
            log.error("令牌刷新失败 - 原因: {}", e.getMessage());
            throw new UnauthorizedException(MessageConstants.TokenInvalid);
        }
    }
}
